package dk.controlme.lego.server;

import com.sun.org.apache.bcel.internal.generic.NEW;

import javax.swing.plaf.metal.MetalIconFactory;
import java.io.*;
import java.net.Socket;
import java.net.URL;
import java.security.Principal;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;

public class Worker extends LegoServer implements HttpConstants, Runnable {

    final static int BUFFER_SIZE = 2048;
    // End Of Line bytes
    static final byte[] EOL = {(byte)'\r', (byte)'\n'};
    // Request buffer
    byte[] inputBuffer;
    // Socket to client we're handling
    private Socket socket;
    // Content-type map
    static Hashtable map = new Hashtable();

    Worker() {
        inputBuffer = new byte[2048];
        socket = null;
    }

    synchronized void setSocket(Socket socket) {
        this.socket = socket;
        notify();
    }

    public synchronized void run() {
        while(true) {
            if(socket == null) {
                // Do nothing -- socket isn't set yet
                try{
                    wait();
                } catch(InterruptedException e) {
                    // This should not happen
                    continue;
                }
            }
            try {
                handleClient();
            } catch(Exception e) {
                e.printStackTrace();
            }
            socket = null;
            Vector pool =  LegoServer.threads;
            synchronized (pool) {
                if(pool.size() >= LegoServer.workers) {
                    // Too many threads, exit this one
                    return;
                } else {
                    pool.addElement(this);
                }
            }
        }
    }

    void handleClient() throws IOException {
        InputStream inputStream = new BufferedInputStream(socket.getInputStream());
        PrintStream printStream = new PrintStream(socket.getOutputStream());

        /* We will block in read for this many milliseconds
        before we fail with java.io.InterruptedIOException
        after which we will abandon the connection          */
        socket.setSoTimeout(LegoServer.timeout);

        // Zero out the buffer from last time
        for(int i = 0 ; i < inputBuffer[i] ; i++) {
            inputBuffer[i] = 0;
        }

        try {
            // Only support HTTP GET/HEAD so only interested in first line
            //TODO: Add support for multiple lines in the HTTP request header.
            int nread = 0, r = 0;
            outerloop:
            while (nread < BUFFER_SIZE) {
                r = inputStream.read(inputBuffer, nread, BUFFER_SIZE - nread);
                if(r == -1){
                    /* EOF */
                    return;
                }
                int i = nread;
                nread += r;
                for(; i < nread ; i++) {
                    if(inputBuffer[i] == (byte)'\n' ||
                            inputBuffer[i] == (byte)'\r') {
                        break outerloop;
                    }
                }
            }

            // Are we doing a GET or just a HEAD?
            boolean doingGet;
            // Beginning of filename
            int index;
            if(inputBuffer[0] == (byte)'G' &&
               inputBuffer[1] == (byte)'E' &&
               inputBuffer[2] == (byte)'T' &&
               inputBuffer[3] == (byte)' ') {
                doingGet = true;
                index = 4;
            } else if(inputBuffer[0] == (byte)'H' &&
                      inputBuffer[1] == (byte)'E' &&
                      inputBuffer[2] == (byte)'A' &&
                      inputBuffer[3] == (byte)'D' &&
                      inputBuffer[4] == (byte)' ') {
                doingGet = false;
                index = 5;
            } else {
                // Unsupported method
                printStream.print("HTTP/1.0 " + HTTP_BAD_METHOD +
                        " unsupported method type: ");
                // Method type
                printStream.write(inputBuffer, 0, 5);
                // End of line
                printStream.write(EOL);
                // Flush it down the stream and cleanup
                printStream.flush();
                socket.close();
                return;
            }

            int i = 0;
            for(i = index ; i < nread ; i++) {
                // fastforward until we hit next space, i.e. we've iterated a word
                if(inputBuffer[i] == (byte)' ') {
                    break;
                }
            }

            String filename = (new String(inputBuffer, 0, index, i-index)).replace('/', File.separatorChar);
            if(filename.startsWith(File.separator)) {
                filename = filename.substring(1);
            }
            File targetFile = new File(LegoServer.root, filename);
            if(targetFile.isDirectory()) {
                File indexFile = new File(targetFile, "index.html");
                if(indexFile.exists()) {
                    targetFile = indexFile;
                }
            }
            // Send headers to match targetFile
            boolean OK = printHeaders(targetFile,printStream);
            if(doingGet) {
                if(OK) {
                    sendFile(targetFile,printStream);
                } else {
                    send404(targetFile,printStream);
                }
            }
        } finally {
             socket.close();
        }
    }

    boolean printHeaders(File targetFile, PrintStream printStream) throws IOException {
        boolean ret = false;
        int returnCode = 0;
        if(!targetFile.exists()) {
            returnCode = HTTP_NOT_FOUND;
            printStream.print("HTTP/1.0 " + HTTP_NOT_FOUND + " not found");
            printStream.write(EOL);
            ret = false;
        } else {
            returnCode = HTTP_OK;
            printStream.print("HTTP/1.0 " + HTTP_OK + " OK");
            printStream.write(EOL);
            ret = true;
        }
        log("From " + socket.getInetAddress().getHostAddress() +
            ": GET "+targetFile.getAbsolutePath()+" --> "+ returnCode);
        printStream.print("Server: LegoServer");
        printStream.write(EOL);
        printStream.print("Date: "+ (new Date()));
        printStream.write(EOL);
        if(ret) {
            if(!targetFile.isDirectory()) {
                // We're working on a file!
                printStream.print("Content-length: "+targetFile.length());
                printStream.write(EOL);
                printStream.print("Last Modified: "+ (new Date(targetFile.lastModified())));
                printStream.write(EOL);
                String name = targetFile.getName();
                int index = name.lastIndexOf('.');
                String contentType = null;
                if(index > 0) {
                    contentType = (String) map.get(name.substring(index));
                }
                if(contentType == null) {
                    contentType = "unknown/unknown";
                }
                printStream.print("Content-type: "+contentType);
                printStream.write(EOL);
            } else {
                // We're working on a directory!
                printStream.print("Content-type: text/html");
                printStream.write(EOL);
            }
        }
        return ret;
    }

    void send404(File targetFile, PrintStream printStream) throws IOException {
        printStream.write(EOL);
        printStream.write(EOL);
        printStream.println("Not found\n\n"+"The requested resource was not found.\n");
    }

    void sendFile(File targetFile, PrintStream printStream) throws IOException {
        InputStream inputStream = null;
        printStream.write(EOL);
        if(targetFile.isDirectory()) {
            // Take advantage of FileURLConnection will parse directory listing into HTML for us
            File index = new File(targetFile, "index.html");
            if(index.exists()) {
                inputStream = new FileInputStream(index);
            } else {
                URL url = new URL("file", "", targetFile.getAbsolutePath());
                inputStream = url.openStream();
            }
        } else {
            inputStream = new FileInputStream(targetFile.getAbsolutePath());
        }

        try {
            int n;
            while ((n = inputStream.read(inputBuffer)) > 0) {
                printStream.write(inputBuffer, 0, n);
            }
        } finally {
            inputStream.close();
        }
    }

    static {
        fillMap();
    }

    static void setSuffix(String k, String v) {
        map.put(k,v);
    }

    static void fillMap() {
        // Some common MIME types
        //TODO: Find a better way / shave the list down as needed.
        setSuffix("", "content/unknown");
        setSuffix(".uu", "application/octet-stream");
        setSuffix(".exe", "application/octet-stream");
        setSuffix(".ps", "application/postscript");
        setSuffix(".zip", "application/zip");
        setSuffix(".sh", "application/x-shar");
        setSuffix(".tar", "application/x-tar");
        setSuffix(".snd", "audio/basic");
        setSuffix(".au", "audio/basic");
        setSuffix(".wav", "audio/x-wav");
        setSuffix(".gif", "image/gif");
        setSuffix(".jpg", "image/jpeg");
        setSuffix(".jpeg", "image/jpeg");
        setSuffix(".htm", "text/html");
        setSuffix(".html", "text/html");
        setSuffix(".text", "text/plain");
        setSuffix(".c", "text/plain");
        setSuffix(".cc", "text/plain");
        setSuffix(".c++", "text/plain");
        setSuffix(".h", "text/plain");
        setSuffix(".pl", "text/plain");
        setSuffix(".txt", "text/plain");
        setSuffix(".java", "text/plain");
    }
}
